package com.dycong.common.common;

import org.apache.commons.lang.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;

/**
 * @author dycong
 * @date 2018/3/21 8:45
 */
public class MyAesTools {

    private final static String ALGORITHM     = "AES";
    private static final int    PW_MAX_LENGTH = 16;

    public static String hMacSha256(byte[] data, byte[] key) {
        try {
            SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);
            return Base64.getEncoder().encodeToString(mac.doFinal(data));
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static byte[] hMacSha256Byte(byte[] data, byte[] key) {
        try {
            SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);
            return mac.doFinal(data);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES 加密 PKCS#5填充
     * 
     * @param byteS 代加密
     * @param pwd 密码
     * @param iv 偏移量
     * @return
     */
    private static byte[] encryptWithIv(byte[] byteS, String pwd, String iv) {
        byte[] byteFina = null;
        Cipher cipher;
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(getKey(pwd), ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes("UTF-8")));
            byteFina = cipher.doFinal(byteS);
        } catch (Exception e) {
            throw new RuntimeException("MyAesTools encryptWithIv error", e);
        } finally {
            cipher = null;
        }
        return byteFina;
    }

    /**
     * 解密用户敏感数据获取用户信息
     *
     * @param sessionKey 数据进行加密签名的密钥
     * @param encryptedData 包括敏感数据在内的完整用户信息的加密数据
     * @param iv 加密算法的初始向量
     */
    public static  String decryptForWxApp(String encryptedData, String sessionKey, String iv) {
        // 被加密的数据
        byte[] dataByte = org.apache.commons.codec.binary.Base64.decodeBase64(encryptedData);
        // 加密秘钥
        byte[] keyByte = org.apache.commons.codec.binary.Base64.decodeBase64(sessionKey);
        // 偏移量
        byte[] ivByte = org.apache.commons.codec.binary.Base64.decodeBase64(iv);
        Cipher cipher;
        try {
            // 如果密钥不足16位，那么就补足. 这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte)0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                return new String(resultByte, "UTF-8");
            }
        } catch (Exception e) {
            throw new RuntimeException("MyAesTools encryptWithIv error", e);
        } finally {
            cipher = null;
        }
        return null;
    }

    /**
     * @param byteS 代加密
     * @param pwd 密码
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] byteS, String pwd) throws Exception {
        byte[] byteFina = null;
        Cipher cipher;
        try {
            cipher = Cipher.getInstance(ALGORITHM);
            SecretKeySpec keySpec = new SecretKeySpec(getKey(pwd), ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byteFina = cipher.doFinal(byteS);
        } catch (Exception e) {
            throw e;
        } finally {
            cipher = null;
        }
        return byteFina;
    }

    /**
     * 密码+偏移量 PKCS#7填充
     * 
     * @param plainText 代加密
     * @param password 密码
     * @param iv 偏移量
     * @return Base64
     * @throws UnsupportedEncodingException 异常
     */
    public static String encryptWithIv(String plainText, String password, String iv)
        throws UnsupportedEncodingException {
        if (StringUtils.isBlank(plainText)) {
            throw new RuntimeException("MyAesTools encryptWithIv toSignText length is 0");
        }
        return Base64.getEncoder().encodeToString(encryptWithIv(plainText.getBytes("UTF-8"), password, iv));

    }

    private static byte[] getKey(String pwd) throws Exception {
        if (pwd.length() >= PW_MAX_LENGTH) {
            String bit128Str = pwd.substring(0, PW_MAX_LENGTH);
            return bit128Str.getBytes("UTF-8");
        } else {
            throw new RuntimeException("MyAesTools password is shorter than 16");
        }
    }

}
